Contents page

Rules for Tools/Loading and Saving


Loading and Saving as part of an Environment

As you may have noticed, there are five additional routines to which the Accessory structure may optionally point: `load()', `save()', `size()', `install()', and `clear()'. These provide a mechanism for attaching Accessory specific data to songs in Bars&Pipes. The first three are used to load and save Accessory data on a song by song basis. The last two are used to install that data once loaded and remove it if no longer needed. These routines are useful if your Accessory associates a set of data with each song. For instance, a patch librarian might save a buffer of system exclusive sound data with each song.

Bars&Pipes keeps track of multiple songs with the Environment structure (see `bars.h'.) The Environment structure is actually an exact duplicate of the top half of the `Functions' structure. It contains the same set of global data that define a song. As a result, each song can have all of its Tracks, Tools, Events, etc., accessed through one Environment. To install an Environment, Bars&Pipes transfers all of the Environment data to the Functions structure.

The `load()', `save()', `size()', `install()', and `clear()' commands all use an Environment structure as a reference, which allows Bars&Pipes to instruct your Accessory to load data for an Environment that is not currently active. Because your Accessory's data is not defined as part of the standard Environment structure, you must keep track of it internally, and use the Environment pointer as a reference to indicate which set of data Bars&Pipes is asking you to act on when it calls your routines.

For example, we have a patch librarian Accessory that keeps a system exclusive buffer of 100 bytes for each song. The buffer data structure looks like this:

struct Sysex {
    struct Sysex *next;         /* Maintain a list. */
    struct Environment *song;   /* Song this belongs to. */
    char buffer[100];           /* Data. */ 
};

   The Accessory keeps a linked list of these buffers, one for each song it has loaded.
When one of its five routines is called by Bars&Pipes, it finds the appropriate Sysex
structure by comparing the Environment pointer in the routine call with the song
pointer in each structure.

   To best explain how each of the routines should work, here's the code for each:

     /* First, define the Sysex list and provide a routine to
   scan it for an Environment match. */ 
   struct Sysex *sysexlist = 0;

struct Sysex * scansysex(environment) struct Environment *environment; {
    struct Sysex *sysex = sysexlist;
    for (;sysex;sysex = sysex->next) {
        if (sysex->song == environment) break;
    }
    return(sysex); 
}

/* A routine to allocate a new Sysex structure and
   place it in the list. */ 
struct Sysex * allocsysex(environment) 
struct Environment *environment; 
{
    struct Sysex *sysex;
    sysex = (struct Sysex *) (*functions->myalloc)(sizeof(struct Sysex),0);
    if (sysex) {
        sysex->song = environment;
        sysex->next = sysexlist;
        sysexlist = sysex;
    }
    return(sysex); 
}

/* This is the load routine.  Find or create the sysex
   structure that matches the environment pointer,
   then read into it.  Also, if the environment pointer
   is functions itself, it is the current active environment.
   Go ahead and install it and since this is a patch
   librarian, send the sysex buffer out the
   serial port using the mythical routine  sendsysex() . */ 
   
load(file,environment,size)
long file;                              /* Fast File io pointer. */ 
struct Environment*environment;        /* Environment. */ 
long size;                             /* Size of segment to read. */ 
{
    char buff[100];
    struct Sysex *sysex;
    geta4();
    sysex = scansysex(environment);
    if (!sysex) sysex = allocsysex(environment);
    if (sysex) {
        (*functions->fastread)(file,sysex->buffer,size);
        if (environment == functions)
            sendsysex(sysex->buffer,size);
    }
    else (*functions->fastread)(file,buff,size);
    return(1); 
}

/* Saving requires two routines: size() and save().  Size()
   just returns the size of the buffer it needs to save,
   while save() actually saves the data. */ 
   
size(environment) 
struct Environment *environment;
{ 
    geta4();
    if (scansysex(environment)) return(100);
    else return(0); 
}

/* To save, the format is always a four byte identifier
   (the id of this Accessory) followed by a four byte
   size, followed by the data.  Return 1 if failed, 0
   if successful. */ 
save(file,environment) 
long file; 
struct Environment *environment;
{
    struct Sysex *sysex;
    long size;
    long id;
    geta4();
    sysex = scansysex(environment);
    size = size(environment);
    id = ID_THISACCESSORY;
    if ((*functions->fastwrite)(file,&id,4) == -1) return(1);
    if ((*functions->fastwrite)(file,&size,4) == -1)
        return(1);
    if (sysex) {
        if ((*functions->fastwrite)
            (file,sysex->buffer,size) == -1) return(1);
    }
    return(0); 
}

/* To install an environment, find the Sysex structure
   that corresponds with it and send it out.  Also,
   change the environment pointer to be functions, since
   it is now installed. */ 
install(environment) 
struct Environment *environment;  /* Environment. */ 
{
    struct Sysex *sysex;
    geta4();
    sysex = scansysex(environment);
    if (sysex) {
        sendsysex(sysex->buffer,100);
        sysex->song = (struct Environment *) functions;
    }
    return(1); 
}

/* To clear an environment, remove it from the linked list
   and deallocate the memory. */

clear(environment) 
struct Environment *environment;  /* Environment. */ 
{
    struct Sysex *sysex, *remove;
    geta4();
    sysex = sysexlist;
    if (sysex->song == environment) {
        sysexlist = sysex->next;
        (*functions->myfree)(sysex,sizeof(struct Sysex));
        return(1);
    }
    else {
        for (;sysex->next;sysex = sysex->next) {
            if (sysex->next->song == environment) {
                remove = sysex->next;
                sysex->next = sysex->next->next;
                (*functions->myfree)
                    (remove,sizeof(struct Sysex));
                return(1);
            }
        }
    }
    return(0); 
}
To compile your accessory, follow the same guidelines for compiling a Tool, including linking with `toolstart.o'.

The entire source code to MuFFy, the MIDI File Format converter, is included on the Rules For Tools disk. It is commented, so feel free to browse through it and figure out how it works. You might be tempted to write your own file converter to translate Bars&Pipes compositions into yet another format.